package com.example.demo.filter;

import com.example.demo.service.CustomUserDetailsService;
import com.example.demo.utils.JwtTokenProvider;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;


@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtTokenProvider tokenProvider;
    @Autowired
    private CustomUserDetailsService customUserDetailsService;
    @Value("${app.jwtExpirationInMs}")
    private Long jwtExpiration;

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {

            String accessToken = request.getHeader("Authorization");
            if (!checkIsTokenAuthorizationHeader(accessToken)) {
                log.info("获取到认证头Authorization的值:[{}]但不是我们系统中登录后签发的。", accessToken);
                filterChain.doFilter(request, response);
                return;
            }
            // 解析 jwt token
            Jws<Claims> jws = tokenProvider.parserAuthenticateToken(getRealAuthorizationToken(accessToken));

            if (null == jws) {
                writeJson(response, "认证token不合法");
                return;
            }
            // token 是否过期
            if (tokenProvider.isJwtExpired(jws)) {
                // 处理过期
                handleTokenExpired(response, request, filterChain);
                return;
            }
            String subject = jws.getBody().getSubject();
            UserDetails userDetails = customUserDetailsService.loadUserByUsername(subject);
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authentication);

        } catch (Exception ex) {
            log.error("Could not set user authentication in security context", ex);
        }

        filterChain.doFilter(request, response);
    }

    /**
     * 处理token过期情况
     *
     * @param response
     * @param request
     * @param filterChain
     * @return
     * @throws IOException
     */
    private void handleTokenExpired(HttpServletResponse response, HttpServletRequest request, FilterChain filterChain) throws IOException, ServletException {
        // 获取刷新 token
        String refreshToken = request.getHeader("Refresh-Token");
        // 检测 refresh-token 是否是我们系统中签发的
        if (!checkIsTokenAuthorizationHeader(refreshToken)) {
            log.debug("获取到刷新认证头:[{}]的值:[{}]但不是我们系统中登录后签发的。", "Refresh-Token", refreshToken);
            writeJson(response, "token过期了，refresh token 不是我们系统签发的");
            return;
        }
        // 解析 refresh-token
        Jws<Claims> refreshTokenJws = tokenProvider.parserAuthenticateToken(getRealAuthorizationToken(refreshToken));

        if (null == refreshTokenJws) {
            writeJson(response, "refresh token不合法");
            return;
        }
        // 判断 refresh-token 是否过期
        if (tokenProvider.isJwtExpired(refreshTokenJws)) {
            writeJson(response, "refresh token 过期了");
            return;
        }
        // 重新签发 token
        String newToken = tokenProvider.generatorJwtToken(refreshTokenJws.getBody().getSubject(), "username", jwtExpiration);
        response.addHeader("Authorization", newToken);

        // 构建认证对象
        Jws<Claims> claimsJws = tokenProvider.parserAuthenticateToken(newToken);
        String subject = claimsJws.getBody().getSubject();
        UserDetails userDetails = customUserDetailsService.loadUserByUsername(subject);
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authentication);

        filterChain.doFilter(request, response);
    }

    /**
     * 判断是否是系统中登录后签发的token
     *
     * @param authorizationHeader
     * @return
     */
    private boolean checkIsTokenAuthorizationHeader(String authorizationHeader) {
        if (StringUtils.isBlank(authorizationHeader)) {
            return false;
        }
        if (!StringUtils.startsWith(authorizationHeader, "Bearer ")) {
            return false;
        }
        return true;
    }

    /**
     * 写 json 数据给前端
     *
     * @param response
     * @throws IOException
     */
    private void writeJson(HttpServletResponse response, String msg) throws IOException {
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        Map<String, String> params = new HashMap(4);
        params.put("msg", msg);
        response.getWriter().print(OBJECT_MAPPER.writeValueAsString(params));
    }

    /**
     * 获取到真实的 token 串
     *
     * @param authorizationToken
     * @return
     */
    private String getRealAuthorizationToken(String authorizationToken) {
        return StringUtils.substring(authorizationToken, "Bearer ".length()).trim();
    }
}
